#ifndef _SRS_h__
#define _SRS_h__

#include <config.h>
#include <GSTenums.h>
#include <buildspec.h>
#include <exceptions/GSTRuntimeException.h>

#include <string>
#include <map>
#include <boost/shared_ptr.hpp>	 

#include <Geometry/boost_serialize_includes.hpp>	

namespace GST {
	namespace ClientUtils {
		class SRSItemDesc;
	}// namespace ClientUtils

namespace Geometry {

	class NoSRS;
	class GSTSRSRef;
	class WKTSRSDef;
	class PROJ4SRSDef;

	template <typename Archive> 
	void registerSRSTypes(Archive &ar)
	{
		ar.template register_type<NoSRS>();
		ar.template register_type<GSTSRSRef>();
		ar.template register_type<WKTSRSDef>();
		ar.template register_type<PROJ4SRSDef>();
	}

	/**
	  *		\defgroup refSRS The Spatial Reference Handling
	  *		@{
	  *
	  *		\section Datamodel
	  *
	  *		In GST the Spatial Reference Systems are stored in a relation called srs in the GST schema. The Table consists of the following columns:
	  		<table>
	  			<tr>
	  				<td><b>id bigserial NOT NULL primary key</b></td>
	  				<td>The GST SRS ID</td>
	  			</tr>
	  			<tr>
	  				<td><b>code_type varchar(50)</b></td>
	  				<td>A code identifier</td>
	  			</tr>
	  			<tr>
	  				<td><b>code varchar(255)</b></td>
	  				<td>A code value</td>
	  			</tr>
	  			<tr>
	  				<td><b>name varchar(255)</b></td>
	  				<td>A human readable label</td>
	  			</tr>
	  			<tr>
	  				<td><b>param varchar(2000)</b></td>
	  				<td>A PROJ4 description of the SRS</td>
	  			</tr>
	  		</table>
	  *		
	  *		Of course you can easily create new SRS definitions by adding rows to this table. E.g. a set of default SRS is provided with GST Storage as
	  *		insert script like the following:
			\code
				INSERT INTO gst.srs (code_type, code, name, param) VALUES ('EPSG', '4326', 'WGS84', '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs');
				INSERT INTO gst.srs (code_type, code, name, param) VALUES ('EPSG', '32633', 'WGS 84 / UTM zone 33N', '+proj=utm +zone=33 +ellps=WGS84 +datum=WGS84 +units=m +no_defs ');
				INSERT INTO gst.srs (code_type, code, name, param) VALUES ('EPSG', '32634', 'WGS 84 / UTM zone 34N', '+proj=utm +zone=34 +ellps=WGS84 +datum=WGS84 +units=m +no_defs');
				INSERT INTO gst.srs (code_type, code, name, param) VALUES ('EPSG', '31467', 'DHDN / 3-degree Gauss-Kruger zone 3', '+proj=tmerc +lat_0=0 +lon_0=9 +k=1 +x_0=3500000 +y_0=0 +ellps=bessel +datum=potsdam +units=m +no_defs');
			\endcode
	  *
	  *		The key of table SRS is automatic incrementing on insert. Thus you don't need to define a value for srs.id in insert commands.
	  *		@note The automatic increment starts with 20.000. SRS with a key below are predefined SRS of GST Storage Setup. All above are SRS added on runtime.
	  *
	  *
	  *		\section ClientAPI
	  *
	  *		There is a more easy way of handling SRS than providing insert scripts.
	  *		Using the API is more comfortable and allows to define SRS in different styles than PROJ4. Here the class SRS comes in.
	  *
	  *		\subsection construct Constructing & Storing a SRS
	  *			To construct a new SRS create a definition with the help of class SRS which is the SRS factory and base class. E.g.
					\code
					SRSPtr new_wgs84 = SRS::FromWKT("GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
					                                "AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
					                                "UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]");
					\endcode
	  *			(You can use any WKT definition that is compatible with GDAL here.)
	  *
	  *			To insert this SRS into the table SRS, use the ClientUtils::NetworkInterface.
					\code
					//NetworkPtr nif; //is still created and connected
					Geometry::GSTSRSRefPtr registered;
					registered = nif->InsertSRS(new_wgs84, "My favorite and new SRS", "EPSG", "4326");
					\endcode
	  *			First parameter is your defined SRS. Next 3 parameters handles user label and code system and code key. If there is a code system for your SRS available (like EPSG), you can use it with the key value pair (code_type, code_value) in 3rd and 4th parameter of InsertSRS().
	  *		
	  *			After InsertSRS() your SRS is inserted in the table srs. You'll get back a reference to a GSTSRSRef which will have the auto increment key of the new created SRS.
	  *			You can get it by
	  			\code
					registered->getID();
				\endcode
	  *		
	  *			Now your SRS is registered and you can use it. E.g. creating classes in that reference system or upload features and specify this SRS as features encoding.
	  *
	  *			@see ClientUtils::NetworkInterface::UploadNewFeature(), ClientUtils::NetworkInterface::GetFeature(), GeometryBuilder::setSRS()
	  *
	  *			@note For uploading and downloading features you don't need to store your SRS. Just construct your SRS by a SRS factory function, like FromWKT() and pass it
	  *			to the ClientUtils::NetworkInterface method. Just for creation of a class with a custom SRS, you need to have a registered SRS (or NoSRS if no spatial reference is required).
	  *
	  *		\subsection Inheritance
	  *			The class SRS provides factory functions for srs description. On the other hand it also is the base class, used by any ClientUtils::NetworkInterface method.
	  *			\image html srs_inheritance_graph.png "Inheritance of SRS"
	  *
	  *			@see SRS::EmptySRS()
	  *			@see SRS::FromWKT()
	  *			@see SRS::FromPROJ4()
	  *			@see SRS::FromGSTSRSID()
	  *			
	  *
	  *		\subsection list List SRS
	  *			Getting a list of all SRS is quite easy. Simply call ClientUtils::NetworkInterface::ListAllSRS().
	  *
	  *
	  *		\subsection update Updating / Deletion of SRS	
	  *			You can update or delete registered SRS. Both methods ClientUtils::NetworkInterface::UpdateSRS() and ClientUtils::NetworkInterface::DeleteSRS() are using the key for SRS matching.
	  *		@}
	  */

class SRS;
class NoSRS;
class GSTSRSRef;
class WKTSRSDef;
class PROJ4SRSDef;
typedef boost::shared_ptr<SRS> SRSPtr;

	/**
	  * This is the base class for Spatial Reference Systems. With it you can construct a SRS description by the Members FromWKT(), FromPROJ4(), FromGSTSRSID().
	  *
	  * @note For more information about this class please refer to \ref refSRS "Spatial Reference Handling"
	  */
class GST_API_EXPORT SRS 
{

public:
	enum Type{NOSRS=0,GSTID,WKT,PROJ4,ESRIWKT};

	/**
	  * Generates an encoding string of the SRS.
	  *
	  * @note For developers: Implement me in derived implementations!
	  */
	virtual std::string encode() const = 0;
	virtual std::string ToString() const = 0;
	virtual std::string getDescription() const { return ""; }
	virtual Type getType() const;
	virtual bool operator==(const SRS &other) const;

	///@name static c'tors
	//@{
	static SRSPtr EmptySRS();
	/**
	  * Constructs SRS from encoding string.
	  * @see encode
	  */
	static SRSPtr FromEncode( const std::string & encodeStr );
	static SRSPtr FromWKT(const std::string & wktDesc);
	static SRSPtr FromPROJ4(const std::string & prj4Desc);
	static SRSPtr FromGSTSRSID(long gst_srs_ident);
	static SRSPtr FromESRIWKT(const std::string & wktDesc);
	static SRSPtr SafePtr( SRSPtr srs );
	//@}

	///boost serialization access
	template <typename Archive> 
	void serialize(Archive &ar, const unsigned int version);

protected:
	Type srs_type;

	SRS(const SRS::Type srs_type);
	SRS()
	{

	}

	friend class boost::serialization::access;
};
BOOST_SERIALIZATION_ASSUME_ABSTRACT( SRS );

template <typename Archive> 
void SRS::serialize(Archive &ar, const unsigned int version)
{
	ar & BOOST_SERIALIZATION_NVP( srs_type );

	if (version < 2 && srs_type == ESRIWKT)
	{
		throw exceptions::GSTRuntimeException(
			__METHOD_NAME__,
			"Can not serialize a ESRI WKT with this API version.");
	}
}


  /**
    * Defines an non defined SRS. This means there is no SRS specified.
	* For inserts and downloads of features with a NoSRS it is assumed,
	* that they have the same SRS as the class your downloading from / uploading to.
    */
class GST_API_EXPORT NoSRS
	: public SRS
{
	friend class boost::serialization::access;

public:
	static const char EncodeIdent;

	NoSRS();
	virtual std::string encode() const;
	virtual std::string ToString() const;

	///boost serialization access
	template <typename Archive> 
	void serialize(Archive &ar, const unsigned int version);
};
typedef boost::shared_ptr<NoSRS> NoSRSPtr;

template <typename Archive> 
void NoSRS::serialize(Archive &ar, const unsigned int version)
{
	ar & boost::serialization::make_nvp("srs_base", boost::serialization::base_object<SRS>(*this) ); 
}


/**
  * This is a registered SRS. It has a key, representing this SRS in the database.
  */
class GST_API_EXPORT GSTSRSRef
	: public SRS
{
	friend class boost::serialization::access;

	long id;

	GSTSRSRef() : SRS(SRS::GSTID) {}

public:
	static const char EncodeIdent;

	GSTSRSRef(const long & GST_SRS_ident);
	virtual std::string encode() const;
	virtual std::string ToString() const;

	long getID() const;
	void setID( const long & id);

	///boost serialization access
	template <typename Archive> 
	void serialize(Archive &ar, const unsigned int version);
};
typedef boost::shared_ptr<GSTSRSRef> GSTSRSRefPtr;

template <typename Archive> 
void GSTSRSRef::serialize(Archive &ar, const unsigned int version)
{
	ar & boost::serialization::make_nvp("srs_base", boost::serialization::base_object<SRS>(*this) ); 
	ar & BOOST_SERIALIZATION_NVP( id ); 
}


/**
  * A Spatial Reference System description by the well known text format (WKT format).
  */
class GST_API_EXPORT WKTSRSDef
	: public SRS
{

	friend class boost::serialization::access;

	std::string wkt;

	WKTSRSDef() : SRS(SRS::WKT)  {}

public:
	static const char EncodeIdent;

	WKTSRSDef(const std::string & wktDesc);
	virtual std::string encode() const;
	virtual std::string ToString() const;
	virtual std::string getDescription() const;

	///boost serialization access
	template <typename Archive> 
	void serialize(Archive &ar, const unsigned int version);
};
typedef boost::shared_ptr<WKTSRSDef> WKTSRSDefPtr;

template <typename Archive> 
void WKTSRSDef::serialize(Archive &ar, const unsigned int version)
{
	ar & boost::serialization::make_nvp("srs_base", boost::serialization::base_object<SRS>(*this) ); 
	ar & BOOST_SERIALIZATION_NVP( wkt );
}


 /**
  * A Spatial Reference System description by the PROJ4 format
  *	http://trac.osgeo.org/proj/.
  */
class GST_API_EXPORT PROJ4SRSDef
	: public SRS
{

	friend class boost::serialization::access;

	std::string prj4DescStr;

	PROJ4SRSDef() : SRS(SRS::PROJ4)  {}

public:
	static const char EncodeIdent;

	PROJ4SRSDef(const std::string & prj4Desc);
	virtual std::string encode() const;
	virtual std::string ToString() const;
	virtual std::string getDescription() const;

	///boost serialization access
	template <typename Archive> 
	void serialize(Archive &ar, const unsigned int version);
};
typedef boost::shared_ptr<PROJ4SRSDef> PROJ4SRSDefPtr;

template <typename Archive> 
void PROJ4SRSDef::serialize(Archive &ar, const unsigned int version)
{
	ar & boost::serialization::make_nvp("srs_base", boost::serialization::base_object<SRS>(*this) ); 
	ar & BOOST_SERIALIZATION_NVP( prj4DescStr );
}

/**
* A Spatial Reference System description by the ESRI WKT format
*/
class GST_API_EXPORT ESRIWKTDef
	: public SRS
{

	friend class boost::serialization::access;

	std::string wktDescStr;

	ESRIWKTDef() : SRS(SRS::ESRIWKT)  {}

public:
	static const char EncodeIdent;

	ESRIWKTDef(const std::string & wktDescStr);
	virtual std::string encode() const;
	virtual std::string ToString() const;
	virtual std::string getDescription() const;

	///boost serialization access
	template <typename Archive>
	void serialize(Archive &ar, const unsigned int version);
};
typedef boost::shared_ptr<ESRIWKTDef> ESRIWKTDefPtr;

template <typename Archive>
void ESRIWKTDef::serialize(Archive &ar, const unsigned int version)
{
	ar & boost::serialization::make_nvp("srs_base", boost::serialization::base_object<SRS>(*this));
	ar & BOOST_SERIALIZATION_NVP(wktDescStr);
}
} //namespace ClientUtils
} //namespace GST

BOOST_CLASS_EXPORT_KEY(GST::Geometry::SRS);
BOOST_CLASS_EXPORT_KEY(GST::Geometry::NoSRS);
BOOST_CLASS_EXPORT_KEY(GST::Geometry::GSTSRSRef);
BOOST_CLASS_EXPORT_KEY(GST::Geometry::WKTSRSDef);
BOOST_CLASS_EXPORT_KEY(GST::Geometry::PROJ4SRSDef);
BOOST_CLASS_EXPORT_KEY(GST::Geometry::ESRIWKTDef);

BOOST_CLASS_VERSION(GST::Geometry::SRS, 2) 
BOOST_CLASS_VERSION(GST::Geometry::NoSRS, 1) 
BOOST_CLASS_VERSION(GST::Geometry::GSTSRSRef, 1) 
BOOST_CLASS_VERSION(GST::Geometry::WKTSRSDef, 1) 
BOOST_CLASS_VERSION(GST::Geometry::PROJ4SRSDef, 1) 
BOOST_CLASS_VERSION(GST::Geometry::ESRIWKTDef, 1)

#endif // _SRS_h__
